using System;
using System.Collections.Generic;
using System.Globalization;
using System.IO;
using System.Linq;
using System.Text;
using System.Text.RegularExpressions;
using System.Windows.Forms;

namespace Behaviorize.CodeGenerator.CodeModel
{
	internal static class FeatureFactory
	{
		public static Feature Create(string spec)
		{
			StringReader reader = new StringReader(spec);
			Feature feature = new Feature();
			string name = reader.ReadLine();

			if (string.IsNullOrEmpty(name)) return null;

			feature.Name = name.Trim();
			feature.Scenarios = new List<Scenario>();

			Regex headRegex = new Regex("((^\\s*In order to )(?<motivation>.+))?\n(^\\s*As a )(?<role>.+)\n(^\\s*I want )(?<goal>.+)\n((^\\s*So that )(?<motivation>.+))?|(^\\s*Description: )(?<description>.+)",
			                            RegexOptions.IgnoreCase | RegexOptions.Multiline | RegexOptions.ExplicitCapture);
			MatchCollection headMatches = headRegex.Matches(spec);

			CreateStoryAndDescription(feature, headMatches);
			CreateScenarios(spec, feature);

			return IsValid(feature) ? feature : null;
		}

		private static bool IsValid(Feature feature)
		{
			return !string.IsNullOrEmpty(feature.Name)
			       && feature.Scenarios != null
			       && feature.Scenarios.Count > 0;
		}

		private static void CreateStoryAndDescription(Feature feature, MatchCollection headMatches)
		{
			foreach (Match headMatch in headMatches)
			{
				if (headMatch.Groups["role"].Success && headMatch.Groups["goal"].Success && headMatch.Groups["motivation"].Success)
				{
					feature.Story = new Story(headMatch.Groups["role"].Value.Trim(),
					                          headMatch.Groups["goal"].Value.Trim(),
					                          headMatch.Groups["motivation"].Value.Trim());
				}
				else if (headMatch.Groups["description"].Success)
				{
					feature.Description = headMatch.Groups["description"].Value.Trim();
				}
			}
		}

		private static void CreateScenarios(string text, Feature feature)
		{
			StringReader reader = new StringReader(text);
			string line = reader.ReadLine();

			while (line != null && !line.ToLowerInvariant().Trim().StartsWith("scenario:"))
			{
				line = reader.ReadLine();
			}

			StringBuilder scenarioText = new StringBuilder();
			do
			{
				scenarioText.AppendLine(line);
				line = reader.ReadLine();
			} while (line != null && !line.ToLowerInvariant().Trim().StartsWith("scenario:"));

			Scenario scenario = ParseScenario(scenarioText.ToString());
			if (scenario != null)
			{
				if (feature.Scenarios.Any(sc => sc.Name == scenario.Name))
				{
					MessageBox.Show(string.Format("There are more than one scenario specified with the name '{0}'. Only the first will be selected.", scenario.Name),
					                "Behaviorize",
					                MessageBoxButtons.OK,
					                MessageBoxIcon.Warning);
				}
				else
				{
					feature.Scenarios.Add(scenario);
				}
			}

			if (line != null) CreateScenarios(line + "\r\n" + reader.ReadToEnd(), feature);
		}

		private static Scenario ParseScenario(string scenarioText)
		{
			Regex regex = new Regex("^Scenario: (?<name>.+$)|^\\s*Given (?<given>.+$)|^\\s*When (?<when>.+$)|^\\s*Then (?<then>.+$)|^\\s*And (?<and>.+$)",
			                        RegexOptions.ExplicitCapture | RegexOptions.IgnoreCase | RegexOptions.Multiline);
			Scenario scenario = new Scenario();
			List<StepDefinition> given = null,
			                     when = null,
			                     then = null;

			scenario.Examples = ParseExamples(scenarioText);

			MatchCollection matches = regex.Matches(scenarioText);

			foreach (Match match in matches)
			{
				if (match.Groups["name"].Success)
				{
					scenario.Name = match.Groups["name"].Value.Trim();
				}
				else if (match.Groups["given"].Success)
				{
					given = new List<StepDefinition> { new StepDefinition(match.Groups["given"].Value.Trim(), scenario) };
				}
				else if (match.Groups["when"].Success)
				{
					when = new List<StepDefinition> { new StepDefinition(match.Groups["when"].Value.Trim(), scenario) };
				}
				else if (match.Groups["then"].Success)
				{
					then = new List<StepDefinition> { new StepDefinition(match.Groups["then"].Value.Trim(), scenario) };
				}
				else if (match.Groups["and"].Success)
				{
					if (then != null)
					{
						then.Add(new StepDefinition(match.Groups["and"].Value.Trim(), scenario));
					}
					else if (when != null)
					{
						when.Add(new StepDefinition(match.Groups["and"].Value.Trim(), scenario));
					}
					else if (given != null)
					{
						given.Add(new StepDefinition(match.Groups["and"].Value.Trim(), scenario));
					}
				}
			}

			if (given != null && when != null && then != null)
			{
				scenario.Given = given;
				scenario.When = when;
				scenario.Then = then;
			}
			else
			{
				return null;
			}

			return scenario;
		}

		private static Dictionary<string, List<object>> ParseExamples(string scenarioText)
		{
			StringReader reader = new StringReader(scenarioText);
			Dictionary<string, List<object>> table = new Dictionary<string, List<object>>();
			string line = reader.ReadLine();

			while (line != null && !line.ToLowerInvariant().Trim().StartsWith("examples:"))
			{
				line = reader.ReadLine();
			}

			int rowCounter = 0;

			Dictionary<int, string> columns = new Dictionary<int, string>();

			while (line != null)
			{
				line = reader.ReadLine();

				if (line == null || (!string.IsNullOrWhiteSpace(line) && !line.Trim().StartsWith("|"))) break;
				
				if (string.IsNullOrWhiteSpace(line))
				{
					continue;
				}

				string[] cols = line.Trim().Split(new[] { '|' }, StringSplitOptions.RemoveEmptyEntries);
				int colCounter = 0;

				if (rowCounter++ == 0)
				{
					foreach (string col in cols)
					{
						columns[colCounter++] = col;
						table.Add(col, new List<object>());
					}
				}
				else
				{
					foreach (string col in cols)
					{
						table[columns[colCounter++]].Add(CastValue(col));
					}
				}
			}

			return table;
		}

		private static object CastValue(string value)
		{
			int i;
			double d;
			bool b;

			if (int.TryParse(value, out i)) return i;
			if (double.TryParse(value, NumberStyles.Number, new NumberFormatInfo{NumberDecimalSeparator = "."}, out d)) return d;
			if (bool.TryParse(value, out b)) return b;

			return value;
		}
	}
}